#ifndef SHADERMANAGER_H
#define SHADERMANAGER_H

#include "opengl_inc.h"
#include "oglchecker.h"

class OGLShaderManager
{
public:
  enum Locations { VERTEX = 0, NORMAL = 1, ID = 2, M_MATRIX = 3,
                         N_MATRIX = 7, MAT_AMBIENT = 11, MAT_DIFFUSE = 12,
                         MAT_SPECULAR = 13, MAT_SHININESS = 14, DATA = 15 };
                        // note : matrices occupy 4 locations
  OGLShaderManager() {}
  enum Shaders {SELECTION, PHONG, FLAT, WIREFRAME, WIREFRAME_SELECTION};
  void compileShaders()
  {
    const GLchar* Phong_vp_str[] = {
    #include "Phong_lit_shader.vert"
    };
    const GLchar* Phong_fp_str[] = {
    #include "Phong_lit_shader.frag"
    };
    const GLchar* selection_vp_str[] = {
    #include "selection.vert"
    };
    const GLchar* selection_fp_str[] = {
    #include "selection.frag"
    };
    const GLchar* flat_vp_str[] = {
    #include "flat.vert"
    };
    const GLchar* flat_fp_str[] = {
    #include "flat.frag"
    };
    const GLchar* wireframe_vp_str[] = {
    #include "wireframe.vert"
    };
    const GLchar* wireframe_fp_str[] = {
    #include "wireframe.frag"
    };
    const GLchar* wireframe_sel_vp_str[] = {
    #include "wireframe_sel.vert"
    };
    const GLchar* wireframe_sel_fp_str[] = {
    #include "wireframe_sel.frag"
    };

    GLuint vp_Phong = glCreateShader(GL_VERTEX_SHADER), 
           fp_Phong = glCreateShader(GL_FRAGMENT_SHADER);
    GLuint vp_selection = glCreateShader(GL_VERTEX_SHADER), 
           fp_selection = glCreateShader(GL_FRAGMENT_SHADER);
    GLuint vp_flat = glCreateShader(GL_VERTEX_SHADER),
           fp_flat = glCreateShader(GL_FRAGMENT_SHADER);
    GLuint vp_wireframe = glCreateShader(GL_VERTEX_SHADER),
           fp_wireframe = glCreateShader(GL_FRAGMENT_SHADER);
    GLuint vp_wireframe_sel = glCreateShader(GL_VERTEX_SHADER),
           fp_wireframe_sel = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(vp_Phong, 1, Phong_vp_str, NULL);
    glShaderSource(fp_Phong, 1, Phong_fp_str, NULL);
    glShaderSource(vp_selection, 1, selection_vp_str, NULL);
    glShaderSource(fp_selection, 1, selection_fp_str, NULL);
    glShaderSource(vp_flat, 1, flat_vp_str, NULL);
    glShaderSource(fp_flat, 1, flat_fp_str, NULL);
    glShaderSource(vp_wireframe, 1, wireframe_vp_str, NULL);
    glShaderSource(fp_wireframe, 1, wireframe_fp_str, NULL);
    glShaderSource(vp_wireframe_sel, 1, wireframe_sel_vp_str, NULL);
    glShaderSource(fp_wireframe_sel, 1, wireframe_sel_fp_str, NULL);
    glCompileShader(vp_Phong);
    glCompileShader(fp_Phong);
    glCompileShader(vp_selection);
    glCompileShader(fp_selection);
    glCompileShader(vp_flat);
    glCompileShader(fp_flat);
    glCompileShader(vp_wireframe);
    glCompileShader(fp_wireframe);
    glCompileShader(vp_wireframe_sel);
    glCompileShader(fp_wireframe_sel);
    OGLChecker::checkShaderCompilation(vp_Phong, "VP Phong");
    OGLChecker::checkShaderCompilation(fp_Phong, "FP Phong");
    OGLChecker::checkShaderCompilation(vp_selection, "VP selection");
    OGLChecker::checkShaderCompilation(fp_selection, "FP selection");
    OGLChecker::checkShaderCompilation(vp_flat, "VP flat");
    OGLChecker::checkShaderCompilation(fp_flat, "FP flat");
    OGLChecker::checkShaderCompilation(vp_wireframe, "VP wireframe");
    OGLChecker::checkShaderCompilation(fp_wireframe, "FP wireframe");
    OGLChecker::checkShaderCompilation(vp_wireframe_sel, "VP wireframe selection");
    OGLChecker::checkShaderCompilation(fp_wireframe_sel, "FP wireframe selection");
    Phong = glCreateProgram();
    selection = glCreateProgram();
    flat = glCreateProgram();
    wireframe = glCreateProgram();
    wireframe_sel = glCreateProgram();
    glAttachShader(Phong, vp_Phong);
    glAttachShader(Phong, fp_Phong);
    glAttachShader(selection, vp_selection);
    glAttachShader(selection, fp_selection);
    glAttachShader(flat, vp_flat);
    glAttachShader(flat, fp_flat);
    glAttachShader(wireframe, vp_wireframe);
    glAttachShader(wireframe, fp_wireframe);
    glAttachShader(wireframe_sel, vp_wireframe_sel);
    glAttachShader(wireframe_sel, fp_wireframe_sel);
    glBindAttribLocation(Phong, VERTEX, "vertex");
    glBindAttribLocation(Phong, NORMAL, "normal");
    glBindAttribLocation(Phong, M_MATRIX, "mMatrix");
    glBindAttribLocation(Phong, N_MATRIX, "nMatrix");
    glBindAttribLocation(Phong, MAT_AMBIENT, "vmat_ambient");
    glBindAttribLocation(Phong, MAT_DIFFUSE, "vmat_diffuse");
    glBindAttribLocation(Phong, MAT_SPECULAR, "vmat_specular");
    glBindAttribLocation(Phong, MAT_SHININESS, "vmat_shininess");
    glBindAttribLocation(selection, VERTEX, "vertex");
    glBindAttribLocation(selection, ID, "vid");
    glBindAttribLocation(selection, M_MATRIX, "mMatrix");
    glBindAttribLocation(flat, VERTEX, "vertex");
    glBindAttribLocation(flat, M_MATRIX, "mMatrix");
    glBindAttribLocation(wireframe, VERTEX, "vertex");
    glBindAttribLocation(wireframe, MAT_AMBIENT, "vColor");
    glBindAttribLocation(wireframe_sel, VERTEX, "vertex");
    glBindAttribLocation(wireframe_sel, ID, "vid");
    glLinkProgram(Phong);
    glLinkProgram(selection);
    glLinkProgram(flat);
    glLinkProgram(wireframe);
    glLinkProgram(wireframe_sel);
    OGLChecker::checkProgramLinking(Phong, "Phong");
    OGLChecker::checkProgramLinking(selection, "selection");
    OGLChecker::checkProgramLinking(flat, "flat");
    OGLChecker::checkProgramLinking(wireframe, "wireframe");
    OGLChecker::checkProgramLinking(wireframe_sel, "wireframe selection");
    glDeleteShader(vp_Phong);
    glDeleteShader(fp_Phong);
    glDeleteShader(vp_selection);
    glDeleteShader(fp_selection);
    glDeleteShader(vp_flat);
    glDeleteShader(fp_flat);
    glDeleteShader(vp_wireframe);
    glDeleteShader(fp_wireframe);
    glDeleteShader(vp_wireframe_sel);
    glDeleteShader(fp_wireframe_sel);
  }
  void selectShader(Shaders s)
  {
    switch(s)
    {
      case PHONG: 
      {
        glUseProgram(Phong); 
        break;
      }
      case SELECTION: 
      {
        glUseProgram(selection); 
        break;
      }
      case FLAT:
      {
        glUseProgram(flat);
        break;
      }
      case WIREFRAME:
      {
        glUseProgram(wireframe);
        break;
      }
      case WIREFRAME_SELECTION:
      {
        glUseProgram(wireframe_sel);
        break;
      }
    }
  }
  GLint uniformLocation(Shaders s, const GLchar* name)
  {
    switch(s)
    {
      case PHONG: return glGetUniformLocation(Phong, name);
      case SELECTION: return glGetUniformLocation(selection, name);
      case FLAT: return glGetUniformLocation(flat, name);
      case WIREFRAME: return glGetUniformLocation(wireframe, name);
      case WIREFRAME_SELECTION: return glGetUniformLocation(wireframe_sel, name);
      default: return -1;
    }
  }
private:
  GLuint wireframe_sel;
  GLuint wireframe;
  GLuint Phong;
  GLuint selection;
  GLuint flat;
};

#endif
